<?php

namespace Xin\Logger;


use Psr\Log\LoggerInterface;

/**
 * @mixin LoggerInterface
 */
class Logger implements LoggerInterface
{
    /**
     * @var callable
     */
    protected static $defaultLoggerResolver;
    /**
     * @var callable
     */
    protected static $databaseLoggerResolver;

    /**
     * @var callable
     */
    protected static $stdoutLoggerResolver;

    /**
     * @var string
     */
    protected $name = '';

    /**
     * @var LoggerInterface[]
     */
    protected $loggers = [];

    /**
     * @var LoggerInterface
     */
    protected $stdoutLogger;

    /**
     * @var LoggerInterface
     */
    protected $dbLogger;

    /**
     * @param LoggerInterface[] $loggers
     */
    public function __construct(array $loggers, string $name = 'app')
    {
        $this->loggers = $loggers;
        $this->name = $name;
    }

    /**
     * @return LoggerInterface[]
     */
    public function getLoggers(): array
    {
        return $this->loggers;
    }

    /**
     * 添加日志器
     * @param LoggerInterface $logger
     * @return $this
     */
    public function appendLogger(LoggerInterface $logger)
    {
        if (in_array($logger, $this->loggers, true)) {
            return $this;
        }

        $this->loggers[] = $logger;

        return $this;
    }

    /**
     * 移除日志器
     * @param LoggerInterface $logger
     * @return $this
     */
    private function removeLogger(LoggerInterface $logger)
    {
        foreach ($this->loggers as $index => $tmpLogger) {
            if ($tmpLogger === $logger) {
                array_splice($this->loggers, $index, 1);
                break;
            }
        }

        return $this;
    }

    /**
     * 启用控制台输出
     * @param bool $enable
     * @return $this
     */
    public function enableStdout(bool $enable = true)
    {
        $stdoutLogger = $this->stdoutLogger();
        if ($enable) {
            $this->appendLogger($stdoutLogger);
        } else {
            $this->removeLogger($stdoutLogger);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function enableStdoutTimePrint()
    {
        $stdoutLogger = $this->stdoutLogger();
        if (method_exists($stdoutLogger, 'enableTimePrint')) {
            $stdoutLogger->enableTimePrint();
        }

        return $this;
    }

    /**
     * @return LoggerInterface
     */
    public function stdoutLogger()
    {
        if (!$this->stdoutLogger) {
            $this->stdoutLogger = static::resolveStdoutLogger($this->name);
        }

        return $this->stdoutLogger;
    }

    /**
     * 启用数据库记录
     * @param bool $enable
     * @return $this
     */
    public function enableDatabase(bool $enable = true)
    {
        $databaseLogger = $this->dbLogger();
        if ($enable) {
            $this->appendLogger($databaseLogger);
        } else {
            $this->removeLogger($databaseLogger);
        }

        return $this;
    }

    /**
     * @return LoggerInterface
     */
    public function dbLogger()
    {
        if (!$this->dbLogger) {
            $this->dbLogger = static::resolveDatabaseLogger($this->name);
        }

        return $this->dbLogger;
    }

    /**
     * @inheritDoc
     */
    public function emergency($message, array $context = []): void
    {
        $this->invoke('emergency', $message, $context);
    }

    /**
     * @inheritDoc
     */
    public function alert($message, array $context = []): void
    {
        $this->invoke('alert', $message, $context);
    }

    /**
     * @inheritDoc
     */
    public function critical($message, array $context = []): void
    {
        $this->invoke('critical', $message, $context);
    }

    /**
     * @inheritDoc
     */
    public function error($message, array $context = []): void
    {
        $this->invoke('error', $message, $context);
    }

    /**
     * @inheritDoc
     */
    public function warning($message, array $context = []): void
    {
        $this->invoke('warning', $message, $context);
    }

    /**
     * @inheritDoc
     */
    public function notice($message, array $context = []): void
    {
        $this->invoke('notice', $message, $context);
    }

    /**
     * @inheritDoc
     */
    public function info($message, array $context = []): void
    {
        $this->invoke('info', $message, $context);
    }

    /**
     * @inheritDoc
     */
    public function debug($message, array $context = []): void
    {
        $this->invoke('debug', $message, $context);
    }

    /**
     * @inheritDoc
     */
    public function log($level, $message, array $context = []): void
    {
        foreach ($this->loggers as $logger) {
            $logger->log($level, $message, $context);
        }
    }

    /**
     * @param string $level
     * @param  $message
     * @param array $context
     * @return void
     */
    private function invoke($level, $message, array $context = [])
    {
        foreach ($this->loggers as $logger) {
            $logger->{$level}($message, $context);
        }
    }

    /**
     * @param string $name
     * @return $this
     */
    public static function logger(string $name = 'app')
    {
        return new static([
            static::resolveDefaultLogger($name),
        ], $name);
    }

    /**
     * 设置数据库日志解析器
     * @param callable $resolver
     * @return void
     */
    public static function setDatabaseLoggerResolver(callable $resolver)
    {
        self::$databaseLoggerResolver = $resolver;
    }

    /**
     * 解析数据库日志器
     * @param string $name
     * @return LoggerInterface
     */
    public static function resolveDatabaseLogger(string $name): LoggerInterface
    {
        if (self::$databaseLoggerResolver === null) {
            throw new \RuntimeException("database logger resolver not set.");
        }

        return call_user_func(self::$databaseLoggerResolver, $name);
    }

    /**
     * 设置标准输出日志解析器
     * @param callable $resolver
     * @return void
     */
    public static function setStdoutLoggerResolver(callable $resolver)
    {
        self::$stdoutLoggerResolver = $resolver;
    }

    /**
     * 解析标准输出日志器
     * @param string $name
     * @return LoggerInterface
     */
    public static function resolveStdoutLogger(string $name): LoggerInterface
    {
        if (self::$stdoutLoggerResolver === null) {
            self::$stdoutLoggerResolver = function ($name) {
                return new StdoutLogger($name);
            };
        }

        return call_user_func(self::$stdoutLoggerResolver, $name);
    }

    /**
     * 设置默认日志解析器
     * @param callable $resolver
     * @return void
     */
    public static function setDefaultLoggerResolver(callable $resolver)
    {
        self::$defaultLoggerResolver = $resolver;
    }

    /**
     * 解析默认日志解析器
     * @param string $name
     * @return LoggerInterface
     */
    public static function resolveDefaultLogger(string $name): LoggerInterface
    {
        if (self::$defaultLoggerResolver === null) {
            throw new \RuntimeException("default logger resolver not set.");
        }

        return call_user_func(self::$defaultLoggerResolver, $name);
    }
}
